package com.diy.sigmund.diycommon.spring.interceptor;

import com.diy.sigmund.diycommon.entity.bo.RuleProcessParam;
import com.diy.sigmund.diycommon.entity.constant.HeaderConstant;
import com.diy.sigmund.diycommon.entity.constant.LogConstant;
import com.diy.sigmund.diycommon.entity.enumerate.WebInterceptorRuleEnum;
import com.diy.sigmund.diycommon.entity.enumerate.WebInterceptorRuleEnumConfig;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisService;
import com.diy.sigmund.diycommon.spring.ContextHolder;
import com.diy.sigmund.diycommon.spring.RequestWrapper;
import com.diy.sigmund.diycommon.spring.anno.IgnoreTokenVerification;
import com.diy.sigmund.diycommon.spring.config.PortConfig;
import com.diy.sigmund.diycommon.util.NetworkUtil;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * Web拦截器
 *
 * @author ylm-sigmund
 * @since 2021/4/24 15:33
 */
public class WebInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;
    @Autowired
    private PortConfig portConfig;
    private static final Logger log = LoggerFactory.getLogger(WebInterceptor.class);

    /**
     * <br>拦截处理程序的执行。在 HandlerMapping 确定适当的处理程序对象之后，但在 HandlerAdapter 调用处理程序之前调用。
     * <br>DispatcherServlet 处理执行链中的处理程序，该处理程序由任意数量的拦截器组成，处理程序本身位于最后。使用此方法，每个拦截器都可以决定中止执行链，通常是发送 HTTP 错误或编写自定义响应。
     * <br>注意：特殊注意事项适用于异步请求处理。有关更多详细信息，请参阅AsyncHandlerInterceptor 。
     * <br>默认实现返回true 。
     * <br>参数：
     * <br>request – 当前的 HTTP 请求
     * <br>response – 当前的 HTTP 响应
     * <br>handler - 选择执行的处理程序，用于类型和/或实例评估
     * <br>回报：
     * <br>如果执行链应该继续下一个拦截器或处理程序本身，则为true 。否则，DispatcherServlet 假定此拦截器已经处理了响应本身。
     * <br>抛出：
     * <br>Exception ——如果出现错误
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        log.info(LogConstant.SPRING_LOG, "Web拦截器处理 开始");
        ContextHolder.getInstance().setStopWatch();

        String requestURI = request.getRequestURI();
        String userAgent = request.getHeader("user-agent");
        log.info("请求路径 {}", requestURI);
        log.info("请求ip是 " + NetworkUtil.getIpAddress(request));
        log.info("本机端口是 " + portConfig.getPort());
        log.info("请求头 user-agent {}", userAgent);

        printRequestWrapperInput(request);
        // 原始入参，按需打印，因为request.getReader()可能导致报错
        // printTheOriginalInput(request);

        // 该注解为忽略token校验注解，少量接口是不需要在请求头传token的，将其忽略
        IgnoreTokenVerification ignoreTokenVerification = null;
        /**
         * handler保存了本次请求的controller也就是接口方法的一些信息，如类，方法，参数等
         * 如果是一次静态资源的请求则该handler不应该是HandlerMethod的实现类
         * 判断是否是一个正常的接口，如果是则进行鉴权操作，不是则直接放行
         */
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        log.info("拦截器规则处理 开始");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 如果方法上标注了该注解，则不走后续校验
        if (method.isAnnotationPresent(IgnoreTokenVerification.class)) {
            log.info("当前接口忽略了token校验");
            return true;
        }
        ignoreTokenVerification = method.getAnnotation(IgnoreTokenVerification.class);
        if (Objects.nonNull(ignoreTokenVerification)) {
            return true;
        }

        String token = request.getHeader(HeaderConstant.TOKEN);
        log.info("外部请求 {}={}", HeaderConstant.TOKEN, token);
        String innerToken = request.getHeader(HeaderConstant.INNER_TOKEN);
        log.info("内部请求 {}={}", HeaderConstant.INNER_TOKEN, innerToken);
        // 获取拦截器规则策略
        WebInterceptorRuleEnum webInterceptorRuleEnum = getWebInterceptorRuleEnum(token, innerToken);
        RuleProcessParam ruleProcessParam = new RuleProcessParam(token, innerToken, redisService);
        // 执行策略
        return webInterceptorRuleEnum.process(ruleProcessParam);
    }

    /** 打印包装后的入参 */
    private void printRequestWrapperInput(HttpServletRequest request) {
        Map<String, Object> parameterMap = RequestWrapper.getParameterMap(request);
        log.info("测试包装HttpServletRequest的工具RequestWrapper（未被包装也可以打印），打印入参 parameterMap={}", parameterMap);
    }

    /** 打印原始入参 */
    private void printTheOriginalInput(HttpServletRequest request) throws IOException {
        BufferedReader br = request.getReader();
        String str;
        StringBuilder wholeParams = new StringBuilder();
        while ((str = br.readLine()) != null) {
            wholeParams.append(str);
        }
        log.info("打印原始入参，解决定位加解密的成本 {}", wholeParams);
    }

    private WebInterceptorRuleEnum getWebInterceptorRuleEnum(String token, String innerToken) {
        if (StringUtils.isNotBlank(token) && StringUtils.isBlank(innerToken)) {
            return WebInterceptorRuleEnumConfig.TOKEN.getRuleEnum();
        }
        if (StringUtils.isBlank(token) && StringUtils.isNotBlank(innerToken)) {
            return WebInterceptorRuleEnumConfig.INNER_TOKEN.getRuleEnum();
        }
        return WebInterceptorRuleEnumConfig.OTHER.getRuleEnum();
    }

    /**
     * <br>拦截处理程序的执行。在 HandlerAdapter 实际调用处理程序之后，但在 DispatcherServlet 呈现视图之前调用。可以通过给定的 ModelAndView 向视图公开其他模型对象。
     * <br>DispatcherServlet 处理执行链中的处理程序，该处理程序由任意数量的拦截器组成，处理程序本身位于最后。使用此方法，每个拦截器都可以对执行进行后处理，以执行链的相反顺序应用。
     * <br>注意：特殊注意事项适用于异步请求处理。有关更多详细信息，请参阅AsyncHandlerInterceptor 。
     * <br>默认实现为空。
     * <br>参数：
     * <br>request – 当前的 HTTP 请求
     * <br>response – 当前的 HTTP 响应
     * <br>handler - 开始异步执行的处理程序（或HandlerMethod ），用于类型和/或实例检查
     * <br>modelAndView – 处理程序返回的ModelAndView （也可以是null ）
     * <br>抛出：
     * <br>Exception ——如果出现错误
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        log.info("modelAndView:{}", JacksonUtil.toJson(modelAndView));
    }

    /**
     * <br>请求处理完成后的回调，即渲染视图后。将在处理程序执行的任何结果上调用，从而允许适当的资源清理。
     * <br>注意：仅当此拦截器的preHandle方法成功完成并返回true时才会调用！
     * <br>与postHandle方法一样，该方法将在链中的每个拦截器上以相反的顺序调用，因此第一个拦截器将是最后一个被调用的拦截器。
     * <br>注意：特殊注意事项适用于异步请求处理。有关更多详细信息，请参阅AsyncHandlerInterceptor 。
     * <br>默认实现为空。
     * <br>参数：
     * <br>request – 当前的 HTTP 请求
     * <br>response – 当前的 HTTP 响应
     * <br>handler - 开始异步执行的处理程序（或HandlerMethod ），用于类型和/或实例检查
     * <br>ex – 处理程序执行时抛出的任何异常，如果有的话；这不包括已通过异常解析器处理的异常
     * <br>抛出：
     * <br>Exception ——如果出现错误
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) throws Exception {
        if (Objects.nonNull(ContextHolder.getInstance().getStopWatch())) {
            ContextHolder.getInstance().remove();
            log.info("清除上下文 成功");
        }
        log.info(LogConstant.SPRING_LOG, "Web拦截器处理 结束");
    }
}
